﻿#region Copyright (c) Lokad 2010
// This code is released under the terms of the new BSD licence.
// URL: http://www.lokad.com/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Lokad.Monitoring.Persistence.Entities;
using Lokad.Monitoring.Persistence.Models;
using Lokad.Monitoring.Persistence.Serialization;
using NHibernate.Criterion;
using NHibernate.Linq;

namespace Lokad.Monitoring.Persistence.Repositories
{
    public class ReportRepository : BaseRepository
    {
        public IEnumerable<Report> List()
        {
            return
                (from e in Session.Linq<Report>()
                 orderby e.Id
                 select e).ToList();
        }

        public void Create(Report report)
        {
            using (var trans = Session.BeginTransaction())
            {
                // HACK: just indicates that report has never been refreshed
                report.LastRefreshed = new DateTime(1901,1,1);
                Session.Save(report);
                trans.Commit();
            }
        }

        public Report Edit(string id)
        {
            return Session.Get<Report>(id);
        }

        public void Edit(string id, Report report)
        {
            using (var trans = Session.BeginTransaction())
            {
                var db = Session.Get<Report>(id);

                db.Endpoints = report.Endpoints;
                db.IndicatorFaultTags = report.IndicatorFaultTags;
                db.IndicatorMinusTags = report.IndicatorMinusTags;
                db.IndicatorPlusTags = report.IndicatorPlusTags;
                db.MessageMinusTags = report.MessageMinusTags;
                db.MessagePlusTags = report.MessagePlusTags;
                db.TimePlotSettings = report.TimePlotSettings;
                // db.IsFault left unchanged on purpose

                Session.Update(db);
                trans.Commit();
            }
        }

        public void Delete(string id)
        {
            using (var trans = Session.BeginTransaction())
            {
                var db = Session.Get<Report>(id);
                Session.Delete(db);
                trans.Commit();
            }
        }

        public RsmDocument Latest(string id)
        {
            var report = Session.Get<Report>(id);
            if (report == null || report.Data == null) return null;

            var stream = new MemoryStream(report.Data);
            stream.Position = 0;

            var serializer = new GzipPoxSerializer();
            return (RsmDocument)serializer.Deserialize(stream, typeof(RsmDocument));
        }

        public RsmDocument Generate(string id)
        {
            var report = Session.Get<Report>(id);
            if (null == report) return null;

            // HACK: magic number to treat 'MaxDays == 0'.
            var threshold = DateTime.UtcNow.AddDays(
                report.MaxDays > 0 ? -report.MaxDays : -10000); // 10k days = 27 years

            var endpoints = Split(report.Endpoints).Convert(n => new Endpoint { Id = n});

            var docs = Session.CreateCriteria(typeof(Response))
                    .Add(Restrictions.In("Endpoint", endpoints))
                    .Add(Restrictions.Gt("Retrieved", threshold))
                    .List<Response>()
                    .OrderByDescending(r => r.Retrieved)
                    .ToList()
                    .Select(ResponseRepository.Convert)
                    .ToList();

            var indicatorMinusTags = Split(report.IndicatorMinusTags);
            var indicatorPlusTags = Split(report.IndicatorPlusTags);
            var messageMinusTags = Split(report.MessageMinusTags);
            var messagePlusTags = Split(report.MessagePlusTags);
            var indicatorFaultTags = Split(report.IndicatorFaultTags);
            var messageFaultTags = Split(report.MessageFaultTags);

            var doc = new RsmDocument
                {
                    Indicators = docs
                        // only the latest indicators matter
                        .Take(1).SelectMany(x => x.Indicators)
                        // positive tags
                        .Where(x => indicatorPlusTags.IsEmpty() || Split(x.Tags).Intersect(indicatorPlusTags).Any())
                        // negative tags
                        .Where(x => !Split(x.Tags).Intersect(indicatorMinusTags).Any())
                        .OrderBy(x => x.Name)
                        .ToArray(),

                    Messages = docs
                        .SelectMany(x => x.Messages).Distinct(x => x.Id)
                        // positive tags
                        .Where(x => indicatorPlusTags.IsEmpty() || Split(x.Tags).Intersect(messagePlusTags).Any())
                        // negative tags
                        .Where(x => !Split(x.Tags).Intersect(messageMinusTags).Any())
                        .OrderByDescending(x => x.Updated)
                        .ToArray()
                };

            
            foreach(var indicator in doc.Indicators)
            {
                indicator.IsFault = Split(indicator.Tags).Intersect(indicatorFaultTags).Any();

                // any of the indicator is fault, then report is fault
                doc.IsFault |= indicator.IsFault;
            }

            foreach(var message in doc.Messages)
            {
                message.IsFault = Split(message.Tags).Intersect(messageFaultTags).Any();
            }

            // saving the report before returning it
            var stream = new MemoryStream();
            var serializer = new GzipPoxSerializer();
            serializer.Serialize(stream, doc);

            using (var trans = Session.BeginTransaction())
            {
                var db = Session.Get<Report>(id);

                db.Data = stream.ToArray();
                db.LastRefreshed = DateTime.UtcNow;
                db.IsFault = doc.IsFault;

                Session.Update(db);
                trans.Commit();
            }

            return doc;
        }

        static string[] Split(string str)
        {
            return string.IsNullOrEmpty(str) ? 
                new string[0] : 
                str.Split().Where(s => !string.IsNullOrEmpty(s)).ToArray();
        }
    }
}
